home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Best Tools for JAVA
/
Best Tools for JAVA.iso
/
JAVA_ALL
/
IDE
/
SUBARTIC
/
RELEASE.ZIP
/
sub_arctic
/
lib
/
html_element.java
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
Macintosh to JP
NeXTSTEP
RISC OS/Acorn
Shift JIS
UTF-8
Wrap
Java Source
|
1996-10-04
|
49.9 KB
|
1,710 lines
package sub_arctic.lib;
import sub_arctic.input.*;
import sub_arctic.output.*;
import sub_arctic.lib.sub_arctic_error;
import sub_arctic.constraints.std_function;
import sub_arctic.constraints.std_constraint_consts;
import java.awt.Font;
import java.awt.FontMetrics;
import java.util.StringTokenizer;
import java.util.Vector;
import java.awt.Color;
/**
* This is a class for keeping around enough state process an
* HTML document. It is probably not of interest to anyone
* other than programmers implementing support for new HTML tags.
* If you want to <I>use</I> HTML in your sub_arctic interfaces,
* look at the text_flow class. <P>
*
* This class keeps track of various display characteristics
* and the current contextual information. It also uses methods to
* generate the various interactors that go on the display and these
* may be overridden to provide support for new (and more complex)
* HTML tags. <P>
*
* Basically this class keeps a static variable which is the
* current "state" of the HTML parse. It is ostensibly a stack
* but because it is searched it various orders, it is implemented
* with a vector. You can use "push_element" and pop_element to
* pop elements of this class onto and off this stack. Thus,
* when a <B> tag is seen a new html_element is pushed
* onto the stack to note the font change; when the matching
* </B> tag is reached, the element is popped off the stack. <P>
*
* The static methods calculate_font(), calculate_color(), and
* calculate_indent() are used to compute the current font, color
* and indentation level whenever the stack is manipulated.
* Thus the variable _current_font is always the up-to-date
* value to use for creating new screen text. If you put values
* in the public slots of the html_element for your specific
* subclass, these methods will pick those values up and add them
* to the current "context" for drawing operations. <P>
*
* The static method calculate_functions performs an <B>important</B>
* speed optimization that users of this code must understand.
* Since you want your new subclasses of html_element to work
* "in context" with other instances of other subclasses,
* you must "advertise" which functions of the API you implement.
* Once you have done so, calculate_functions will determine
* when to call your object and when to ignore it. This is
* a speed optimization which allows us to avoid walking the
* state stack every time we want to insert a word of text (or
* do any other parsing operation). For example, the subclass
* numbered_element advertises that it implements the method
* list_item_prefix because all it cares about doing is inserting
* the correct prefix when a new LI tag is seen. A more complex
* example might be if you wanted some "context dependent" tag
* handling (such as supporting new tags which are only valid
* inside other tags) you would advertise that you implement
* the handle_tag operation. <P>
*
* The way you advertise what functions you implement is by
* overriding the method functions_implemented and returning a set
* of constants (bitwise ored together) from the static constants
* below. <P>
*
* The biggest weakness of this object right now is that it really
* can't handle anything that spans rows. Once we finish putting
* up a row, we throw everything about it away. This is probably
* going to make it hard in the future to do "align=RIGHT" type
* of stuff in IMG tags since to make it look nice it should span
* multiple rows of text.<P>
*
*/
public class html_element implements std_constraint_consts {
/*****************************************************************/
/* STATIC CONSTANTS */
/*****************************************************************/
/*
* These are the static constants that you should return
* from the method functions_implemented
* (ored together) for the set of functions your object
* implements
*/
public static final int LIST_ITEM_PREFIX = 1;
public static final int HANDLE_TAG = 2;
public static final int ADD_SPACE = 4;
public static final int ADD_WORD = 8;
public static final int END_OF_LINE = 16;
public static final int ADD_LIST_ITEM = 32;
public static final int HANDLE_AMP = 64;
public static final int STRING_END = 128;
public static final int PARAGRAPH_END = 256;
public static final int PARAGRAPH_START = 512;
/*****************************************************************/
/* STATIC STATE VARIABLES */
/*****************************************************************/
/**
* This is basically a data structure for keeping track of
* the current state of the HTML parse so we can image correctly.
* We add html_elements to it and it gets walked to do things
* like generate the current font and the current indentation level.
*/
protected static Vector state;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is where we store the current font as we are doing a
* layout pass.
*/
protected static Font _current_font;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This variable is the amount of indentation to use if a new line is
* called for.
*/
protected static int _current_indent;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the row that we are currently constructing.
*/
protected static row _current_row;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the current set of colors in use.
*/
protected static color_pair _current_colors;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This the text flow we are actually working on.
*/
protected static text_flow _current_flow;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the current column (which is really the
* current paragraph) we are building.
*/
protected static column _current_column;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* LIST_ITEM_PREFIX.
*/
protected static html_element _current_list_item_prefix;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* HANDLE_TAG
*/
protected static html_element _current_handle_tag;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* ADD_SPACE
*/
protected static html_element _current_add_space;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* ADD_WORD
*/
protected static html_element _current_add_word;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* END_OF_LINE
*/
protected static html_element _current_end_of_line;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* ADD_LIST_ITEM.
*/
protected static html_element _current_add_list_item;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* HANDLE_AMP.
*/
protected static html_element _current_handle_amp;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* STRING_END
*/
protected static html_element _current_string_end;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* PARAGRAPH_END.
*/
protected static html_element _current_paragraph_end;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is a pointer to the html_element which is handling
* PARAGRAPH_START
*/
protected static html_element _current_paragraph_start;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the string tokenizer we are using.
*/
protected static StringTokenizer _tokenizer;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the amount of space between children.
*/
protected static int _inter_child_space = 0;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is where we hold the string we are currently
* building. This is used for the performance optimization
* so we don't have so many labels.
*/
protected static StringBuffer _current_buffer;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is where we keep track of how wide the string
* is that we have buffered in _current_buffer. This
* is to avoid having to consult the FontMetrics
* every time.
*/
protected static int _current_buffer_width;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the basic font size that the layout algorithm
* is going to use
*/
protected static int _basic_font_size=12;
/*****************************************************************/
/* STATIC METHODS FOR SETTING VARS */
/*****************************************************************/
/**
* We need this for people who want to have switches for
* setting the size. This will not take effect until the
* next parse of an HTML document.
*
* @param int s the new basic font size
*/
public static void set_basic_font_size(int s) {
_basic_font_size=s;
}
/*****************************************************************/
/* STATIC METHODS FOR PARSING HTML */
/*****************************************************************/
/**
* This function is called to initialize the parse and get the
* state stack set up for future things.
*
* @param text_flow tf the text_flow we are laying out
* @param String text the text to put in the text_flow
*/
protected static void init(text_flow tf,String text) {
/* this guy is the root! */
html_element he=new html_element(true);
/* set the state up */
state=new Vector(50);
/* set up the tokenizer */
_tokenizer=new StringTokenizer(text," \t\n<",true);
/* set the current text flow */
_current_flow=tf;
/* get the string buffer read */
_current_buffer=new StringBuffer();
/* set the current column to be a column with the same width as
* the current flow */
/* DANGER: IF YOU MODIFY THIS don't forget to modify
the one in paragraph_start() too */
_current_column=new column(0 /*ignored*/,0 /* ignored */,
_current_flow.w()-(2*_current_flow.border()),
10 /* ignored */,
0 /* no border because parent is doing it*/,
_current_flow.interchild_space(),
false,false,false /* crucial */,
column.LEFT_JUSTIFIED,
null);
/* we want these guys to size their height by their children */
_current_column.set_h_constraint(std_function.offset(LAST_CHILD.Y2(), 0));
/* this is something of a hack, but we have a dependency
* that is tough to get around. You'd like it to be the
* case that every time you change the state stack the
* current string buffered up is flushed into the
* display. However, we are trying to get the state stack
* initialized right now and thus _current_string_end
* won't have a value yet. So, we are going to initialize
* it to a dead value, knowing it will get overwritten
* the first time the functions get calculated. trust me.*/
_current_string_end=new html_element();
/* default font at font size in the text flow's font */
he.font_name=_current_flow.font_name();
/* set the font size to be the one for this flow */
set_basic_font_size(_current_flow.font_size());
he.font_size=_basic_font_size;
/* push it on the stack */
push_element(he);
/* setup the first row */
/* WARNING: if you modify this don't forget to modify the one
* in end_of_line */
_current_row=new row(0/*border */,_inter_child_space,
false,false,row.BOTTOM_JUSTIFIED);
/* this setting of the size to zero is to insure that the row's
initial width (if any) could throw off the calculations of
the size of a row*/
_current_row.set_w(0);
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function is called to handle the end of the HTML parse
* and general close down operations.
*
*/
protected static void finish() {
/* clean up */
_current_end_of_line.end_of_line(0,false);
/* finish this paragraph */
_current_paragraph_end.paragraph_end();
/* we're done, so prevent things from working if we got into
this code again */
_current_row=null;
_current_column=null;
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function pushes things on the state vector. It also
* forces a recalculation of the current indentation level
* and current font.
*
* @param html_element he the html element to push onto the state
*/
protected static void push_element(html_element he) {
/* before we go changing the state, we need to tell the
string handling code what's happening */
_current_string_end.string_end();
state.addElement(he);
/* recalculate the state */
calculate_font();
calculate_indent();
calculate_colors();
calculate_functions();
}
//had:
//* @exception bad_value PROPAGATED
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function removes things from the state vector. It
* also forces a recalculation of the current indentation
* level.
*
* @return html_element although the element removed is returned, its not
* clear what use that is.
*/
protected static html_element pop_element() {
html_element el;
if (state.isEmpty()) {
throw new sub_arctic_error("State stack empty! Probably caused by " +
"badly formed HTML");
}
/* before we go changing the state, we need to tell the
string handling code what's happening */
_current_string_end.string_end();
el=(html_element)state.elementAt(state.size()-1);
state.removeElementAt(state.size()-1);
/* recalculate the state */
calculate_font();
calculate_indent();
calculate_colors();
calculate_functions();
/* return the element */
return el;
}
//had:
//* @exception bad_value is thrown if the stack is empty when you try to pop it
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function calculates the current font by walking the
* state vector. It's policy is that it uses the most current
* font and the most current font size. It does a binary or of
* all font modifiers found. Note that the the state is walked
* from oldest element to newest element.
*
*/
protected static void calculate_font() {
String name=null;
int size=0,mods=0,i;
html_element he;
/* validity check */
if (state.size()==0) {
throw new sub_arctic_error("States stack is empty!");
}
/* walk the state */
for (i=0; i<state.size(); ++i) {
he=(html_element)state.elementAt(i);
/* copy the values */
if (he.font_name!=null) {
name=he.font_name;
}
if (he.font_size!=0) {
size=he.font_size;
}
/* always or in this value... it can't hurt! */
mods|=he.font_modifier;
}
/* validity check the result */
if ((name==null) ||
(size==0)) {
throw new sub_arctic_error("The state stack didn't produce a valid font");
}
/* set the font */
_current_font=new Font(name,mods,size);
}
//had:
//* @exception bad_value will be thrown if insufficient font information
//* is found to generate a font.
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function computes the current indent level by walking
* down the state and adding up the current amount of indentation.
*/
protected static void calculate_indent() {
int i,indent=0;
html_element he;
/* validity check */
if (state.size()==0) {
throw new sub_arctic_error("States stack is empty!");
}
/* walk the state */
for (i=0; i<state.size(); ++i) {
he=(html_element)state.elementAt(i);
/* ok add this one in */
indent+=he.indent_contribution;
}
/* totaled it up, stuff it in */
_current_indent=indent;
}
//had:
//* @exception bad_value is thrown if the state stack is empty
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function calculates the current color to be drawing
* in. If it doesn't find anything, it uses the default system
* colors.
*/
public static void calculate_colors() {
int i;
html_element he;
color_pair cp=null;
/* walk the state */
for (i=state.size()-1; i>=0; --i) {
he=(html_element)state.elementAt(i);
if (he.colors!=null) {
cp=he.colors;
break;
}
}
/* figure it out ... if its null then we use the system defaults */
if (cp==null) {
_current_colors=manager.default_color_pair();
} else {
_current_colors=cp;
}
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function calculates the current set of objects which are
* handling parsing operations. This basically walks backwards
* through the state stack looking at what functions each
* object is advertising that it implements. If one is found,
* it is put in the slot for whatever functions it implements.
* This process proceeds until the root is encountered or
* state stack is emptied.
*
*/
public static void calculate_functions() {
int i;
int implemented;
html_element el;
/* we start out looking for everything */
int mask=LIST_ITEM_PREFIX | HANDLE_TAG | ADD_SPACE | END_OF_LINE |
ADD_LIST_ITEM | ADD_WORD | HANDLE_AMP | STRING_END | PARAGRAPH_END |
PARAGRAPH_START;
/* work our way backwards */
for (i=state.size()-1; i>=0; --i) {
el=(html_element)state.elementAt(i);
/* retrieve the ones they are using */
implemented=el.functions_implemented();
/* test for function */
if (((mask & LIST_ITEM_PREFIX)!=0) &&
((implemented & LIST_ITEM_PREFIX)!=0)) {
_current_list_item_prefix=el;
/* we don't need it anymore */
mask ^= LIST_ITEM_PREFIX;
}
/* test for function */
if (((mask & HANDLE_TAG)!=0) &&
((implemented & HANDLE_TAG)!=0)) {
_current_handle_tag=el;
/* we don't need it anymore */
mask ^= HANDLE_TAG;
}
/* test for function */
if (((mask & ADD_SPACE)!=0) &&
((implemented & ADD_SPACE)!=0)) {
_current_add_space=el;
/* we don't need it anymore */
mask ^= ADD_SPACE;
}
/* test for function */
if (((mask & ADD_WORD)!=0) &&
((implemented & ADD_WORD)!=0)) {
_current_add_word=el;
/* we don't need it anymore */
mask ^= ADD_WORD;
}
/* test for function */
if (((mask & END_OF_LINE)!=0) &&
((implemented & END_OF_LINE)!=0)) {
_current_end_of_line=el;
/* we don't need it anymore */
mask ^= END_OF_LINE;
}
/* test for function */
if (((mask & ADD_LIST_ITEM)!=0) &&
((implemented & ADD_LIST_ITEM)!=0)) {
_current_add_list_item=el;
/* we don't need it anymore */
mask ^= ADD_LIST_ITEM;
}
/* test for function */
if (((mask & HANDLE_AMP)!=0) &&
((implemented & HANDLE_AMP)!=0)) {
_current_handle_amp=el;
/* we don't need it anymore */
mask ^= HANDLE_AMP;
}
/* test for function */
if (((mask & STRING_END)!=0) &&
((implemented & STRING_END)!=0)) {
_current_string_end=el;
/* we don't need it anymore */
mask ^= STRING_END;
}
/* test for function */
if (((mask & PARAGRAPH_END)!=0) &&
((implemented & PARAGRAPH_END)!=0)) {
_current_paragraph_end=el;
/* we don't need it anymore */
mask ^= PARAGRAPH_END;
}
/* test for function */
if (((mask & PARAGRAPH_START)!=0) &&
((implemented & PARAGRAPH_START)!=0)) {
_current_paragraph_start=el;
/* we don't need it anymore */
mask ^= PARAGRAPH_START;
}
/* if we have all the functions, we can bail out */
if (mask==0) break;
}
/* we are ostensibly done, we should check to make sure everything
* worked */
if (mask!=0) {
throw new sub_arctic_error("Not all parsing functions implemented");
}
}
//had:
//* @exception bad_value gets thrown if it can't find any object on the
//* state stack willing to handle one of the functions.
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function is useful for "backchaining" in the state
* stack to an "older" version of a function. E.g. you are
* implementing some new context-dependent tags and you find
* a tag you are not interested in, just call this function
* with the HANDLE_TAG mask and yourself as the object to
* look behind and you'll get returned an object on which
* you can call the handle_tag method and hand it the tag
* it should process.<p>
*
* This function might also be useful for finding out who
* is "behind" you in the context. E.g. if you wanted to
* make the numbering tags do dots between subordinate
* lists. E.g. the second level list would 2.1, 2.2,
* 2.3, etc.
*
* @param int fn the function code to find (must be one of the
* static constants above.
* @param html_element elem the object to start looking "behind" (note that
* this function will return null if the element is
* the root).
* @return html_element element implementing the function in question.
*/
public static html_element previous_function(int fn, html_element elem) {
int index=state.indexOf(elem),i;
html_element el;
/* bad element? */
if (index==-1) {
throw new sub_arctic_error("Element is not in the state stack");
}
/* the root? */
if (index==0) {
return null;
}
/* normal case */
for (i=index-1; i>=0; --i) {
el=(html_element)state.elementAt(i);
if ((el.functions_implemented() & fn)!=0) {
return el;
}
}
/* didn't find it, something's wrong */
throw new sub_arctic_error("Unable to locate the function " + fn +
" in any object in the state stack");
}
//had:
//*@exception bad_value is thrown if no matching object is found (this
//* shouldn't happen if the root is set up right) or you
//* pass a bad function constant or if the object passed
//* as the element is not in the state stack.
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the main driver function for this parsing operation.
*/
public static void parse() {
String tok;
/* loop over all the tokens */
while (_tokenizer.hasMoreTokens()) {
/* look for a normal token */
tok=_tokenizer.nextToken(" \t\n<&");
/* is it the beginning of a tag ? */
if (tok.indexOf("<")!=-1) {
/* fish out the tag by changing the delimiter */
tok=_tokenizer.nextToken(">");
/* tell the tag handling process about it */
_current_handle_tag.handle_tag(tok);
/* we know there is a greater than pending so lets get
rid of it */
_tokenizer.nextToken(">");
continue;
}
/* if it starts with an ampersand its a special */
if (tok.startsWith("&")) {
tok=_tokenizer.nextToken(";");
_current_handle_amp.handle_amp(tok);
/* get rid of the extra ; */
_tokenizer.nextToken(";");
continue;
}
/* it doesn't have a less than in it, so let's try looking
for space, newline and or tab */
if ((tok.indexOf(" ")!=-1) ||
(tok.indexOf("\t")!=-1) ||
(tok.indexOf("\n")!=-1)) {
/* its some sort of whitespace, tell us about it */
_current_add_space.add_space();
continue;
}
/* ok, we now know its a plain old word so let's just add it */
_current_add_word.add_word(tok);
}
/* we are done with it */
finish();
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function determines if a row is empty or not.
* @param row r the row to check
* @return boolean true if the row is empty
*/
public static boolean row_is_empty(row r) {
if ((r.num_children()==0) ||
((r.num_children()==1) &&
(r.child(0) instanceof spacer))) {
return true;
}
return false;
}
//had:
//* @exception general PROPAGATED
/*****************************************************************/
/* INSTANCE VARIABLES */
/*****************************************************************/
/**
* This variable tells us if we are the root of the state stack
* or not. If this is true we will export all the functions in
* the API to their default values.
*/
private boolean root;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/** use this for tags that actually change the base font */
/* if this is NULL it is ignored */
public String font_name;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/** use this for tags that SET the size of the font */
/* if this is 0 it gets ignored */
public int font_size;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/** this is a font modifier for things like bold and italic
* it gets ORed into the font modifier mask ... since plain is
* zero you can just let plain be the value if you don't want
* anything snazzy */
public int font_modifier;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/** this is the amount of indentation that is tag's contribution
* to future rows. Its not an absolute amount but a contribution */
public int indent_contribution;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is the color_pair to use for drawing the text. If you
* don't do anything, you'll get null and the system will use
* the default system colors.
*/
public color_pair colors;
/*****************************************************************/
/* OVERRIDABLE METHODS */
/*****************************************************************/
/**
* Construct a html_element. This constructor makes all the fields
* have ignored values and this object is "dead" with respect
* to what functions it implements.
*/
public html_element() {
root=false;
font_name=null;
font_size=0;
font_modifier=0;
indent_contribution=0;
colors=null;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Construct an HTML element which is the root of the state
* stack by passing true here. You should ONLY do this if you
* intend that all objects below this one in the state stack
* have their functions ignored (which is only likely to be
* useful if you are putting this at the bottom of the
* state stack. This still makes all the fields have
* their default (ignored) values.
*
* @param boolean b true if this object is a root object.
*
*/
public html_element(boolean b) {
root=b;
font_name=null;
font_size=0;
font_modifier=0;
indent_contribution=0;
colors=null;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Override this method to inform the system what parsing
* methods you handle.
*/
public int functions_implemented() {
if (root) {
/* if we are the root, we implement everything */
return (LIST_ITEM_PREFIX | HANDLE_TAG | ADD_SPACE | END_OF_LINE |
ADD_LIST_ITEM | ADD_WORD | HANDLE_AMP | STRING_END |
PARAGRAPH_END | PARAGRAPH_START);
} else {
/* if we aren't a root, we are a pass thru */
return 0;
}
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function is called to generate a prefix interactor for
* list elements. It should return an interactor to put right in
* front of the actual item. Thus subclasses like the
* numbered_element return a label with the correct number
* in it for this function...
*
*/
public interactor list_item_prefix()
{
return null;
}
//had:
//* @exception general may be thrown by subclasses
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function gets called to implement a tags behavior. Since
* Java can't handle pointers to functions, you'll just have to
* check for each string and then add your code at the appropriate
* place.
*/
protected void handle_tag(String tag)
{
/*
* Get the <P> tag
*/
if (tag.equalsIgnoreCase("P")) {
spacer sp=new spacer(1,manager.get_metrics(_current_font).getAscent());
/* finish this line */
_current_end_of_line.end_of_line(0,false);
/* finish this paragraph */
_current_paragraph_end.paragraph_end();
/* start a new paragraph */
_current_paragraph_start.paragraph_start();
/* put a spacer in that is the height of a T */
_current_row.add_child(sp);
/* finish that line */
_current_end_of_line.end_of_line(0,true);
/* finish blank paragraph */
_current_paragraph_end.paragraph_end();
/* start a new paragraph */
_current_paragraph_start.paragraph_start();
/* done */
return;
}
/*
* We are going to intentionally ignore the /P tag so things
* will work ok if people form their HTML "correctly."
*/
if (tag.equalsIgnoreCase("/P")) {
return;
}
/*
* Get the <BR> tag.
*/
if (tag.equalsIgnoreCase("BR")) {
/* line is done */
_current_end_of_line.end_of_line(0,false);
return;
}
/*
* I don't know why you would use the /BR tag but just in case...
*/
if (tag.equalsIgnoreCase("/BR")) {
return;
}
/**
* The <CENTER> tag
*/
if (tag.equalsIgnoreCase("CENTER")) {
/* implies end of line */
_current_end_of_line.end_of_line(0,false);
/* end of paragraph */
_current_paragraph_end.paragraph_end();
/* push us the stack */
push_element(new centered_element());
/* now start a new paragraph */
_current_paragraph_start.paragraph_start();
}
/* pop off the center object from the state stack */
if (tag.equalsIgnoreCase("/CENTER")) {
/* end the line */
_current_end_of_line.end_of_line(0,false);
/* end of paragraph */
_current_paragraph_end.paragraph_end();
/* remove us from the state stack */
pop_element();
/* start over */
_current_paragraph_start.paragraph_start();
}
/*
* The <LI> tag is one that needs to be deal with specially.
*/
if (tag.equals("LI")) {
_current_add_list_item.add_list_item();
return;
}
/*
* <B> tag
*/
if (tag.equalsIgnoreCase("B")) {
/* create a new element with bold in it and push it */
html_element he=new html_element();
he.font_modifier=Font.BOLD;
push_element(he);
}
/*
* </B>
*/
if (tag.equalsIgnoreCase("/B")) {
pop_element();
}
/*
* <I> tag
*/
if (tag.equalsIgnoreCase("I")) {
/* create a new element with bold in it and push it */
html_element he=new html_element();
he.font_modifier=Font.ITALIC;
push_element(he);
}
/*
* </I>
*/
if (tag.equalsIgnoreCase("/I")) {
pop_element();
}
/**
* <OL> tag
*/
if (tag.equalsIgnoreCase("OL")) {
html_element he = new numbered_element();
push_element(he);
}
/*
* </OL>
*/
if (tag.equalsIgnoreCase("/OL")) {
pop_element();
}
/**
* <UL> tag
*/
if (tag.equalsIgnoreCase("UL")) {
html_element he = new non_numbered_element();
push_element(he);
}
/*
* </UL>
*/
if (tag.equalsIgnoreCase("/UL")) {
pop_element();
}
/*
* H1 is really big (24 pt)
*/
if (tag.equalsIgnoreCase("H1")) {
html_element he=new html_element();
he.font_size=_basic_font_size+12;
he.font_modifier=Font.BOLD;
push_element(he);
}
/*
* H2 is pretty big (16 pt)
*/
if (tag.equalsIgnoreCase("H2")) {
html_element he=new html_element();
he.font_size=_basic_font_size+4;
he.font_modifier=Font.BOLD;
push_element(he);
}
/*
* H3 is big (14 pt)
*/
if (tag.equalsIgnoreCase("H3")) {
html_element he=new html_element();
he.font_size=_basic_font_size+2;
he.font_modifier=Font.BOLD;
push_element(he);
}
/*
* H4 is normal sized (12 pt)
*/
if (tag.equalsIgnoreCase("H4")) {
html_element he=new html_element();
he.font_size=_basic_font_size;
he.font_modifier=Font.BOLD;
push_element(he);
}
/**
* H5 is smaller than normal (10 pt)
*/
if (tag.equalsIgnoreCase("H5")) {
html_element he=new html_element();
he.font_size=_basic_font_size-2;
he.font_modifier=Font.BOLD;
push_element(he);
}
/**
* H6 is much smaller than normal (8 pt)
*/
if (tag.equalsIgnoreCase("H6")) {
html_element he=new html_element();
he.font_size=_basic_font_size-4;
he.font_modifier=Font.BOLD;
push_element(he);
}
/**
* Any tag staring with /H we do the same thing
*/
if (tag.toUpperCase().startsWith("/H")) {
pop_element();
_current_end_of_line.end_of_line(0,false);
}
/**
* KBD (keyboard) is one that I use a lot ... You get a courier
* font for that "computerish" look.
*/
if (tag.equalsIgnoreCase("KBD")) {
html_element he=new html_element();
he.font_name="Courier";
push_element(he);
}
/*
* /KBD
*/
if (tag.equalsIgnoreCase("/KBD")) {
pop_element();
}
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function gets called to add whitespace to the display.
*/
protected void add_space()
{
int w;
/* if we are at the left edge of a row, don't bother */
if (row_is_empty(_current_row) && (_current_buffer.length()==0)) {
return;
}
/* ask the manager how wide a space is */
w=manager.get_metrics(_current_font).stringWidth(" ");
/* are we going to force a new string? */
if (w+_current_buffer_width >= _current_column.w()) {
/* don't bother, we'll just end this line */
_current_end_of_line.end_of_line(0,false);
return;
}
/* check to see if there is a string buffer contents now */
if (_current_buffer.length()!=0) {
/* the buffer has a string in it, so just add a space char */
_current_buffer.append(" ");
/* update how much space the string occupies */
_current_buffer_width+=w;
return;
}
/* nothing buffered, use a spacer */
spacer sp=new spacer(w,1);
/* put the space in there */
_current_row.add_child(sp);
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function does a calculation to see if the new word will fit
* on this line. If it does, it just adds it to the current row.
* If not, it creates a new line and adds the word to that line,
* without considering if it fits. (Note: This means that words
* that are wider than the width of the parent just get clipped.)
*
* @param String word the text of the word to add
*/
protected void add_word(String word)
{
int parent_width=_current_column.w(), row_width=_current_row.w();
int string_width;
FontMetrics fm=manager.get_metrics(_current_font);
/* this is the string we are adding to the display plus
any buffered text's width */
string_width=fm.stringWidth(word) + _current_buffer_width;
/* case 1: row is empty or has only a spacer on it: we always
* put this word on such a line */
if (row_is_empty(_current_row) && (_current_buffer.length()==0)) {
/* we always add it because otherwise you'll get a blank line
if this object is larger than the overall flow */
_current_buffer.append(word);
/* set the buffer width appropriately */
_current_buffer_width=string_width;
return;
}
/* is it too big? */
if (string_width + row_width + _inter_child_space > parent_width) {
/* case 2: too big ... finish this line and make a new one*/
_current_end_of_line.end_of_line(0,false);
/* put the string on the next line */
_current_buffer.append(word);
/* store the amount of text we have left */
_current_buffer_width=fm.stringWidth(word);
} else {
/* case 3: this is the normal case */
_current_buffer.append(word);
/* remember how much we have buffered */
_current_buffer_width=string_width;
}
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function is called to inform us that the current string
* needs to be inserted into the row. This allows us (usually)
* to make one big label for the string, rather than having
* a string for each word (which really slows things down).
* If you are implementing tags which will insert things into
* the current row, you'll probably need to call this function
* to make sure all the text is there.
*
*/
public void string_end()
{
label l;
/* empty strings means no work */
if (_current_buffer.length()==0) return;
/* not empty so make a label */
l=new label(_current_buffer.toString(),_current_font);
/* fix the spacing */
l.set_above_spacing(0);
/* XXX should be figuring out the descent size ... it appears that
the descent size in many cases is WAY larger than it needs to be
so just use 3 instead XX */
/* l.set_below_spacing(3); */
l.set_below_spacing(manager.get_metrics(_current_font).getDescent());
l.set_h_spacing(0);
/* set the colors */
l.set_draw_colors(_current_colors);
_current_row.add_child(l);
/* reset the buffer */
_current_buffer=new StringBuffer();
/* reset the buffer's size */
_current_buffer_width=0;
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function gets called to polish off the current line.
* It takes the current row and adds to the current column
* and then sets up a new row. If you supply an
* argument, that much is subtracted from the normal indentation
* for this row. This is highly useful for keeping things aligned
* in the presence of list items marking the paragraphs.
*
* @param int shift the amount you want subtracted from the indentation.
* @param boolean hard true if you are sure you want to <I>force</I> the
* end of a line, even if you are at the beginning of
* an empty row. Use false to indicate that you just
* want to start at the beginning of a line.
*/
public void end_of_line(int shift, boolean hard)
{
/* deal with soft end of line */
if (hard==false) {
/* is this an empty row? */
if (row_is_empty(_current_row) && (_current_buffer.length()==0)) {
/* its empty and they wanted a soft eol */
return;
}
}
/* make sure we put in the buffered text */
_current_string_end.string_end();
/* put the row we just finished into the column (us) */
_current_column.add_child(_current_row);
/* WARNING: if you modify this don't forget to modify the one
* in init(...) */
_current_row=new row(0,_inter_child_space,
false,false,row.BOTTOM_JUSTIFIED);
/* this setting of the size to zero is to insure that the row's
initial width (if any) could throw off the calculations of
the size of a row*/
_current_row.set_w(0);
/* if we don't need any space, don't bother */
if (_current_indent==0) return;
/* we only make this one pixel high because we figure if
* if any text gets put in it will make the object size right */
_current_row.add_child(new spacer(_current_indent-shift,1));
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Insert a list item. This ends up causing whatever the current
* object for this lists iteration to be inserted.
*
*/
public void add_list_item()
{
interactor marker;
int i;
html_element el;
/* get the most current list item implementation */
marker=_current_list_item_prefix.list_item_prefix();
/* did we get out with nothing found? */
if (marker==null) {
System.out.println("BAD <LI> tag! No list in progress!");
return;
}
/* the normal thing is to finish the current line and then add the
marker */
_current_end_of_line.end_of_line(marker.w(),false);
_current_row.add_child(marker);
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This function gets called to handle putting things it that
* are used with the ampersand notation.
* @param String s the string that was between the ampersand and the semicolon
*/
protected void handle_amp(String tok)
{
/* check for ones we know */
if (tok.equals("quot")) {
_current_add_word.add_word("\"");
} else if (tok.equals("lt")) {
_current_add_word.add_word("<");
} else if (tok.equals("gt")) {
_current_add_word.add_word(">");
}
/* didn't recognize it, give up */
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Handle the end of the current paragraph. This function takes
* the current column and shoves it into the text flow. It is
* most commonly called as P is encountered.
*/
public void paragraph_end() {
_current_flow.add_child(_current_column);
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Handle the start of a new paragraph. This default of
* this just makes the current column be left justified.
* It is most commonly called when a P is encountered,
* but can be called by tags like CENTER.
*/
public void paragraph_start() {
/* DANGER: IF YOU MODIFY THIS don't forget to modify
the one in init() too */
_current_column=new column(0 /*ignored*/,0 /* ignored */,
_current_flow.w()-(2*_current_flow.border()),
10 /* ignored */,
0 /* parent is doing border */,
_current_flow.interchild_space(),
false,false,false /* crucial */,
column.LEFT_JUSTIFIED,
null);
_current_column.
set_h_constraint(std_function.offset(LAST_CHILD.Y2(), 0));
}
}
/*****************************************************************/
/* SOME EXAMPLE CLASSES FOR COMMON TAGS */
/*****************************************************************/
/**
* This private class is used to keep track of the numbering
* and generate the numbers for numbered lists.
*/
class numbered_element extends html_element {
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* This is what number we are about to output.
*/
int count;
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Construct a numbered element.
*/
public numbered_element() {
super();
count=1;
indent_contribution=10;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* We overrode for the purpose of informing the system that
* we implement the LIST_ITEM_PREFIX method.
*
* @return int bitmask of the functions implemented
*/
public int functions_implemented() {
return LIST_ITEM_PREFIX;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Generate the next list item prefix, which is a number.
*
*/
public interactor list_item_prefix()
{
int c=count;
label l;
/* update count */
count++;
/* make the label */
l=new label((new Integer(c)).toString(),_current_font);
l.set_above_spacing(0);
/* XXX should be figuring out the descent size ... it appears that
the descent size in many cases is WAY larger than it needs to be
so just use 3 instead XX */
/* l.set_below_spacing(3); */
l.set_below_spacing(manager.get_metrics(_current_font).getDescent());
l.set_h_spacing(2);
return l;
}
}
//had:
//* @exception general PROPAGATED
/*---------------------------------------------------------------------*/
/**
* This is a private class which is used to draw a bullet. This class
* has no input behavior it just draws a little circle in the middle
* of its area.
*/
class bullet extends base_interactor {
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Construct one of these dots.
* @param int w the width of its bounding rectangle
* @param int h the height of its bounding rectangle
*/
public bullet(int w,int h)
{
super(0,0,w,h);
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Draw the little dot.
* @param drawable d the surface to draw on
*/
public void draw_self_local(drawable d) {
int x=w()/2,y=h()/2;
Color c=d.getColor(); // stash this for a sec
d.setColor(html_element._current_colors.foreground());
d.fillArc(x,y-2,4,4,0,360);
d.setColor(c);
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
}
/*---------------------------------------------------------------------*/
/**
* This private class is used to generate the bullets for
* non-numbered lists.
*/
class non_numbered_element extends html_element {
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Construct a numbered element.
*/
public non_numbered_element() {
super();
indent_contribution=10;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Generate the next list item prefix, which is a number.
*/
public interactor list_item_prefix()
{
return new bullet(10,manager.get_metrics(_current_font).getAscent()+
manager.get_metrics(_current_font).getDescent());
}
//had:
//* @exception general PROPAGATED
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* We overrode for the purpose of informing the system that
* we implement the LIST_ITEM_PREFIX method.
*
* @return int bitmask of the functions implemented
*/
public int functions_implemented() {
return LIST_ITEM_PREFIX;
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
}
/*---------------------------------------------------------------------*/
/**
* This class is used to implement the CENTERED tag. It just changes
* the type of column that gets used to put the rows in.
*/
class centered_element extends html_element {
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Construct a centered element.
*/
public centered_element() {
super();
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* We overrode for the purpose of informing the system that
* we implement the PARAGRAPH_START method.
*
* @return int bitmask of the functions implemented
*/
public int functions_implemented() {
return (PARAGRAPH_START);
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
/**
* Handle the start of a new paragraph. We want to create
* a centered column.
*/
public void paragraph_start() {
_current_column=new column(0 /*ignored*/,0 /* ignored */,
_current_flow.w()-(2*_current_flow.border()),
10 /* ignored */,
0/* parent is doing border */,
_current_flow.interchild_space(),
false,false,false,
column.CENTER_JUSTIFIED,
null);
_current_column.
set_h_constraint(std_function.offset(LAST_CHILD.Y2(), 0));
}
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */
}
/*---------------------------------------------------------------------*/
/*=========================== COPYRIGHT NOTICE ===========================
This file is part of the subArctic user interface toolkit.
Copyright (c) 1996 Scott Hudson and Ian Smith
All rights reserved.
The subArctic system is freely available for most uses under the terms
and conditions described in
http://www.cc.gatech.edu/gvu/ui/sub_arctic/sub_arctic/doc/usage.html
and appearing in full in the lib/interactor.java source file.
The current release and additional information about this software can be
found starting at: http://www.cc.gatech.edu/gvu/ui/sub_arctic/
========================================================================*/